#! /usr/bin/python2.4
# jhebert@cs.washington.edu

""" This class handles maintaining state wrt other charts. """

import time
import Queue

class GridRepository:

  def __init__(self, ports):
    udpPort, tcpPort = ports
    self.tcpPort = tcpPort
    self.udpPort = udpPort
    self.lastBroadcastTime = 0
    self.messagesToSend = Queue.Queue()
    self.broadcastTimeout = 60
    self.recvTimeout = 60
    self.sentTimeout = 5
    self.received = {}
    self.sent = {}

    self.nodeTable = {}
    #TODO: better track of state of other nodes and
    # messages sent to them.
    #TODO: Don't send state to self.

  def Update(self, messages):
    """ Update status for each received message and update
    stale state wrt other nodes. """
    for message in messages:
      try:
        toReply, node = '', None
        data,address=message
        command,port=data.split(':')
        replyAddr=(address[0], int(port))
        if(address[0] in self.nodeTable):
          node = self.nodeTable[address[0]]
        else:
          node = NodeStatus(replyAddr)
          self.nodeTable[address[0]]=node
        toReply = node.Update(command)
        if(len(toReply)>0):
          self.SendMessage(replyAddr, toReply)

        #self.UpdateEntry(address, replyAddr)
        #if(command=='broadcast'):
        #  toReply='ping:'+str(self.tcpPort)
        #elif(command=='ping'):
        # toReply='pong:'+str(self.tcpPort)
        #elif(command=='pong'):
        #  pass
        #if(len(toReply)>0):
        #  self.SendMessage(replyAddr, toReply)
      except ValueError, err:
        pass
    self.UpdateStaleEntries()

#  def UpdateEntry(self, address, replyAddr):
#    """ Store reply address and last contact time. """
#    self.received[address] = (replyAddr, time.time())
    
  def UpdateStaleEntries(self):
    """ Broadcast and redo status if needed. """
    currTime = time.time()
    oldTime=self.lastBroadcastTime
    if((oldTime==0)|(currTime-oldTime>self.broadcastTimeout)):
      self.BroadcastStatus()
    for addr in self.nodeTable:
      node = self.nodeTable[addr]
      message = node.UpdateStale()
      if(len(message)>0):
        self.SendMessage(node.addr, message)




    #for address in self.received:
    #  replyAddr, oldTime = self.received[address]
    #  if(currTime-oldTime>self.recvTimeout):
    #    message = 'ping:'+str(self.tcpPort)
    #    if(replyAddr in self.sent):
    #      diff=currTime-self.sent[replyAddr]
    #      if(diff<self.sentTimeout):
    #        continue
    #      else:
    #        print 'Lack of reply?!'
    #    else:
    #      print "haven't sent?"
    #    self.SendMessage(replyAddr, message)

  def SendMessage(self, addr, message):
    """ Send message to address. TODO: should bookkeep this. """
    self.sent[addr] = time.time()
    self.messagesToSend.put((addr, message))

  def BroadcastStatus(self):
    """ Broadcast tcp location to updPort. """
    message='broadcast:'+str(self.tcpPort)
    address = ('255.255.255.255', self.udpPort)
    self.messagesToSend.put((address, message))    
    self.lastBroadcastTime = time.time()


class NodeStatus:

  def __init__(self, address):
    ip,port = address
    self.addr = address
    self.ipAddr = ip
    self.tcpPort = port
    self.receivedTime = None
    self.sentTime = None
    self.receivedMessage = None
    self.sentMessage = None
    self.receivedTimeout = 60
    self.sentTimeout = 5
    

  def Update(self, message):
    """ Update data structures for time and messages.
    Also determine the message to send next. """
    self.receivedMessage = message
    self.receivedTime = time.time()
    toReturn=''
    if(message=='broadcast'):
      toReturn='ping:'+str(self.tcpPort)
      self.sentMessage = 'ping'
      self.sentTime = time.time()
    elif(message=='ping'):
      toReturn='pong:'+str(self.tcpPort)
      self.sentMessage = 'pong'
      self.sentTime = time.time()
    elif(message=='pong'):
      toReturn=''
    return toReturn

  def UpdateStale(self):
    currTime = time.time()
    toReturn = ''
    if(currTime-self.receivedTime>self.receivedTimeout):
      toReturn='ping:'+str(self.tcpPort)
    if(currTime-self.sentTime>self.sentTimeout):
      if(self.sentMessage=='ping'):
        if(self.sentTime>self.receivedTime):
          toReturn='ping:'+str(self.tcpPort)
          self.sentTime = time.time()
    return toReturn
